%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use: %reload_ext autoreload
Data have been taken from http://www.lazyportfolioetf.com .
from collections import OrderedDict
from src.domain.share_type import StockInfo, BondInfo, Term, Region, Cap
from src.statistics import get_max_first_year
from src.laxy_portfolio_etf import get_returns_data
stocks_data = OrderedDict({
"VTI": get_returns_data("data/monthly-returns/vti-returns-monthly.txt"),
"EAFE": get_returns_data("data/monthly-returns/eafe-returns-monthly.txt"),
"VUG": get_returns_data("data/monthly-returns/vug-returns.txt"),
"Russell 2000": get_returns_data("data/monthly-returns/russell_2000-returns-monthly.txt"),
"EAFE Small-Cap": get_returns_data("data/monthly-returns/eafe-small-cap-return-monthly.txt"),
"VNQ (REIT)": get_returns_data("data/monthly-returns/vnq-returns.txt"),
"GLD": get_returns_data("data/monthly-returns/gld-returns.txt"),
"DBC": get_returns_data("data/monthly-returns/dbc-returns.txt"),
"BND": get_returns_data("data/monthly-returns/bnd-returns.txt"),
"SHY": get_returns_data("data/monthly-returns/shy-returns.txt"),
})
shares_info = OrderedDict({
"VTI": StockInfo(region=Region.US, cap=Cap.Large),
"VUG": StockInfo( region=Region.US, cap=Cap.Large),
"VNQ (REIT)": StockInfo(region=Region.US, cap=Cap.Large),
"EAFE": StockInfo( region=Region.ExUS, cap=Cap.Large),
"Russell 2000": StockInfo(region=Region.US, cap=Cap.Small),
"EAFE Small-Cap": StockInfo(region=Region.ExUS, cap=Cap.Small),
"GLD": StockInfo(region=Region.US, cap=Cap.Large),
"DBC": StockInfo(region=Region.US, cap=Cap.Large),
"BND": BondInfo(region=Region.US, term=Term.Long),
"SHY": BondInfo(region=Region.US, term=Term.Short),
})
max_first_year = get_max_first_year(stocks_data.values())
print("max_first_year", max_first_year)
max_first_year 1992
import numpy as np
import pandas as pd
import plotly
import plotly.express as px
from src.statistics import get_max_returns_from_same_year, get_max_first_year, calc_returns_amount_list
from src.plot_utils import get_monthly_data_range
plotly.offline.init_notebook_mode()
max_first_year = get_max_first_year(stocks_data.values())
init_amount = 1000
data = [calc_returns_amount_list(init_amount, x) for x in get_max_returns_from_same_year(stocks_data.values())]
df = pd.DataFrame(
np.array(data).transpose(),
columns=list(stocks_data.keys()),
index=get_monthly_data_range(max_first_year, 2021)
)
fig = px.line(df, x=df.index, y=df.columns)
fig.show(renderer="notebook+pdf")
import numpy as np
from src.statistics import get_correlation_table_view, get_stocks_std_view, calc_annual_geometric_mean
stock_names = list(stocks_data.keys())
corr_table = get_correlation_table_view(stocks_data)
index = stock_names
columns = ["From year", "Mean ret.", "Std", "|", *stock_names]
splitter_column = ["|"] * len(index)
from_column = [x.first_year for x in stocks_data.values()]
annual_mean_column = [f'{round(calc_annual_geometric_mean(x), 2)}%' for x in stocks_data.values()]
std_column = get_stocks_std_view(stocks_data)
df = pd.DataFrame(
np.array([from_column, annual_mean_column, std_column, splitter_column, *corr_table]).transpose(),
columns=columns,
index=index
)
df
| From year | Mean ret. | Std | | | VTI | EAFE | VUG | Russell 2000 | EAFE Small-Cap | VNQ (REIT) | GLD | DBC | BND | SHY | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| VTI | 1972 | 10.98% | 15.55% | | | - | 70.62% | 96.05% | 89.34% | 73.6% | 61.69% | 1.88% | 17.08% | 16.64% | 4.38% |
| EAFE | 1986 | 7.08% | 17.4% | | | 70.62% | - | 65.17% | 62.51% | 85.57% | 50.36% | 12.83% | 32.65% | 9.71% | -8.3% |
| VUG | 1972 | 11.1% | 16.8% | | | 96.05% | 65.17% | - | 80.92% | 66.28% | 52.26% | 0.84% | 12.25% | 17.49% | 5.74% |
| Russell 2000 | 1985 | 10.13% | 19.74% | | | 89.34% | 62.51% | 80.92% | - | 71.96% | 62.29% | -3.13% | 24.19% | 0.76% | -19.11% |
| EAFE Small-Cap | 1992 | 7.95% | 16.47% | | | 73.6% | 85.57% | 66.28% | 71.96% | - | 57.33% | 18.85% | 47.89% | 5.91% | -21.97% |
| VNQ (REIT) | 1972 | 11.48% | 17.09% | | | 61.69% | 50.36% | 52.26% | 62.29% | 57.33% | - | 5.77% | 14.96% | 18.19% | 3.73% |
| GLD | 1972 | 7.6% | 19.9% | | | 1.88% | 12.83% | 0.84% | -3.13% | 18.85% | 5.77% | - | 27.34% | 7.79% | 7.08% |
| DBC | 1971 | 8.21% | 18.7% | | | 17.08% | 32.65% | 12.25% | 24.19% | 47.89% | 14.96% | 27.34% | - | -3.46% | -1.32% |
| BND | 1972 | 6.72% | 5.3% | | | 16.64% | 9.71% | 17.49% | 0.76% | 5.91% | 18.19% | 7.79% | -3.46% | - | 87.64% |
| SHY | 1977 | 5.54% | 3.03% | | | 4.38% | -8.3% | 5.74% | -19.11% | -21.97% | 3.73% | 7.08% | -1.32% | 87.64% | - |
from src.domain.distribution import SharesDistribution
from src.domain.share_type import Cap, Term, Region, ShareType
TOTAL = 1
print(dict(zip((Term.Long, ), (1, ))))
print(Region.US.value)
distribution = SharesDistribution(
by_type=dict(zip((ShareType.Stock, ShareType.Bond), (0.75, 0.25))),
by_region=dict(zip((Region.US, Region.ExUS), (0.7, 0.3))),
by_cap=dict(zip((Cap.Large, Cap.Small), (0.7, 0.3))),
by_term=dict(zip((Term.Long, ), (1, ))),
)
large_us_stocks = {
"VTI": 0.8,
"VNQ (REIT)": 0.2
}
large_exus_stocks = {
"EAFE": 1
}
small_us_stocks = {
"Russell 2000": 1
}
small_exus_stocks = {
"EAFE Small-Cap": 1
}
bonds = {
"BND": 1
}
def assert_shares(obj):
assert sum(obj.values()) == TOTAL
fund_shares = [large_us_stocks, large_exus_stocks, small_us_stocks, small_exus_stocks, bonds]
for shares in [distribution.by_cap, distribution.by_region, distribution.by_term, distribution.by_type] + fund_shares:
assert_shares(shares)
{<Term.Long: 'long'>: 1}
US
flatten_stock_portfolio = []
stock_share = distribution.by_type[ShareType.Stock]
for (region_key, region_value) in distribution.by_region.items():
for (cap_key, cap_value) in distribution.by_cap.items():
flatten_stock_portfolio.append({
"region": region_key.value,
"share": stock_share * region_value * cap_value,
"cap": cap_key.value
})
def sort_by_share(portfolio):
return sorted(portfolio, key=lambda x: x["share"], reverse=True)
flatten_stock_portfolio = sort_by_share(flatten_stock_portfolio)
import pandas as pd
def share_to_percent(share):
return f'{round(share * 100, 2)}%'
def prepare_stock_table_rows(flatten_portfolio):
stocks_table = []
full_share = 0
for row in flatten_portfolio:
full_share += row["share"]
for stock in flatten_portfolio:
share = stock["share"]
stocks_table.append([
share_to_percent(share / full_share),
share_to_percent(share)
])
return stocks_table
def get_stock_name(share):
return f'{share["region"].capitalize()} {share["cap"].capitalize()}-cap stocks'
stocks_table = prepare_stock_table_rows(flatten_stock_portfolio)
df = pd.DataFrame(stocks_table, columns=['Share', 'Share of all'], index=[get_stock_name(x) for x in flatten_stock_portfolio])
df
| Share | Share of all | |
|---|---|---|
| Us Large-cap stocks | 49.0% | 36.75% |
| Us Small-cap stocks | 21.0% | 15.75% |
| Ex-us Large-cap stocks | 21.0% | 15.75% |
| Ex-us Small-cap stocks | 9.0% | 6.75% |
import pandas as pd
from IPython.display import display
from src.portfolio import prepare_stock_table_rows, get_stock_name, get_fund_distribution, calc_portfolio_std, calc_portfolio_returns
from src.statistics import calc_annual_geometric_mean_from_monthly
stocks_table = prepare_stock_table_rows(flatten_stock_portfolio)
df = pd.DataFrame(stocks_table, columns=['Share', 'Share of all'], index=[get_stock_name(x) for x in flatten_stock_portfolio])
display(df)
view_fund_distrib = get_fund_distribution(fund_shares, shares_info, distribution)
percent_column = [f'{round(x * 100, 2)}%' for x in view_fund_distrib.values()]
df2 = pd.DataFrame(np.array(percent_column).transpose(), columns=['%'], index=list(view_fund_distrib.keys()))
display(df2)
portfolio_std = calc_portfolio_std(view_fund_distrib, stocks_data)
display(f'Portfolio standard deviation: {round(portfolio_std, 2)}%')
portfolio_returns, max_first_year = calc_portfolio_returns(view_fund_distrib, stocks_data)
display(f'Portfolio annual mean returns: {round(calc_annual_geometric_mean_from_monthly(portfolio_returns), 2)}%')
init_amount = 1000
portoflio_returns_amounts = calc_returns_amount_list(init_amount, portfolio_returns)
df = pd.DataFrame(
np.array(portoflio_returns_amounts).transpose(),
columns=["Portfolio"],
index=get_monthly_data_range(max_first_year, 2021)
)
fig = px.line(df, x=df.index, y=df.columns)
fig.show(renderer="notebook+pdf")
| Share | Share of all | |
|---|---|---|
| Us Large-cap stocks | 49.0% | 36.75% |
| Us Small-cap stocks | 21.0% | 15.75% |
| Ex-us Large-cap stocks | 21.0% | 15.75% |
| Ex-us Small-cap stocks | 9.0% | 6.75% |
| % | |
|---|---|
| VTI | 29.4% |
| VNQ (REIT) | 7.35% |
| EAFE | 15.75% |
| Russell 2000 | 15.75% |
| EAFE Small-Cap | 6.75% |
| BND | 25.0% |
'Portfolio standard deviation: 11.24%'
'Portfolio annual mean returns: 8.62%'
import dataclasses
from src.plot_utils import get_monthly_data_range
default_distribution = SharesDistribution(
by_type=dict(zip((ShareType.Stock, ShareType.Bond), (0.75, 0.25))),
by_region=dict(zip((Region.US, Region.ExUS), (0.7, 0.3))),
by_cap=dict(zip((Cap.Large, Cap.Small), (0.7, 0.3))),
by_term=dict(zip((Term.Long, ), (1, ))),
)
def create_distrib_by_type_ratio(stock_rate: float):
bond_rate = 1 - stock_rate
return dataclasses.replace(default_distribution, by_type=dict(zip((ShareType.Stock, ShareType.Bond), (stock_rate, bond_rate))))
distributions_rates = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
distributions_names = [f'{round(x * 100)}/{round((1 - x) * 100)}' for x in distributions_rates]
distributions = [create_distrib_by_type_ratio(x) for x in distributions_rates]
distributions_by_fund = [get_fund_distribution(fund_shares, shares_info, x) for x in distributions]
percent_columns = [[f'{round(x * 100, 2)}%' for x in distrib.values()] for distrib in distributions_by_fund]
df = pd.DataFrame(np.array(percent_columns).transpose(), columns=distributions_names, index=list(distributions_by_fund[0].keys()))
display(df)
distributions_std = [f'{round(calc_portfolio_std(x, stocks_data), 2)}%' for x in distributions_by_fund]
max_first_year = 1992
distributions_returns = [calc_portfolio_returns(x, stocks_data)[0] for x in distributions_by_fund]
distributions_annual_mean = [f'{round(calc_annual_geometric_mean_from_monthly(x), 2)}%' for x in distributions_returns]
df = pd.DataFrame(np.array([distributions_annual_mean, distributions_std]).transpose(), columns=["Mean ret.", "Std"], index=distributions_names)
display(df)
init_amount = 1000
distributions_returns_amounts = [calc_returns_amount_list(init_amount, x) for x in distributions_returns]
df = pd.DataFrame(
np.array(distributions_returns_amounts).transpose(),
columns=distributions_names,
index=get_monthly_data_range(max_first_year, 2021)
)
fig = px.line(df, x=df.index, y=df.columns)
fig.show(renderer="notebook+pdf")
| 0/100 | 10/90 | 20/80 | 30/70 | 40/60 | 50/50 | 60/40 | 70/30 | 80/20 | 90/10 | 100/0 | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| VTI | 0.0% | 3.92% | 7.84% | 11.76% | 15.68% | 19.6% | 23.52% | 27.44% | 31.36% | 35.28% | 39.2% |
| VNQ (REIT) | 0.0% | 0.98% | 1.96% | 2.94% | 3.92% | 4.9% | 5.88% | 6.86% | 7.84% | 8.82% | 9.8% |
| EAFE | 0.0% | 2.1% | 4.2% | 6.3% | 8.4% | 10.5% | 12.6% | 14.7% | 16.8% | 18.9% | 21.0% |
| Russell 2000 | 0.0% | 2.1% | 4.2% | 6.3% | 8.4% | 10.5% | 12.6% | 14.7% | 16.8% | 18.9% | 21.0% |
| EAFE Small-Cap | 0.0% | 0.9% | 1.8% | 2.7% | 3.6% | 4.5% | 5.4% | 6.3% | 7.2% | 8.1% | 9.0% |
| BND | 100% | 90.0% | 80.0% | 70.0% | 60.0% | 50.0% | 40.0% | 30.0% | 20.0% | 10.0% | 0% |
| Mean ret. | Std | |
|---|---|---|
| 0/100 | 5.12% | 3.69% |
| 10/90 | 5.66% | 3.71% |
| 20/80 | 6.17% | 4.31% |
| 30/70 | 6.67% | 5.28% |
| 40/60 | 7.14% | 6.46% |
| 50/50 | 7.6% | 7.76% |
| 60/40 | 8.02% | 9.12% |
| 70/30 | 8.43% | 10.53% |
| 80/20 | 8.81% | 11.96% |
| 90/10 | 9.16% | 13.41% |
| 100/0 | 9.49% | 14.87% |
Research current P/E, P/B by sectors
Move data initialization to Python scripts